#!/usr/bin/env perl
use strict;
use utf8;
use Config;
use Time::HiRes qw /usleep/;
binmode(STDOUT, ":utf8");

sub get_soc_temp {
    my $t=shift;
    my @fnames=(
          '/sys/devices/virtual/thermal/thermal_zone0/temp',
          '/sys/devices/platform/scpi/scpi:sensors/hwmon/hwmon0/temp1_input',
       );
    my $temp = "Unknown";
    for my $fname (@fnames) {
        if( -f $fname ) {
            open my $fh, "<", $fname;
	    if($t) {
                $temp = sprintf "%0.1f", <$fh> / 1000;
            } else {
                $temp = sprintf "%0.1f℃", <$fh> / 1000;
            }
            close $fh;
            return $temp;
        }
    }

    return $temp;
}

sub get_cpu_freq {
    my $f=shift;
    my @fnames=</sys/devices/system/cpu/cpufreq/policy?/cpuinfo_cur_freq>;
    my $freq_str = "";
    for (my $i=0; $i < $#fnames + 1; $i++) {
        my $fname = $fnames[$i];
        open my $fh, "<", $fname;
	usleep(50000);
	my $freq;
	if($f =~ /-F(\d*)/) {
	    my $alert_mhz = 1500;
	    if($1 ne "") {
		$alert_mhz = $1;
	    }
	    my $color = "\x1b[92m";
            $freq = <$fh> / 1000;
	    if($freq > $alert_mhz) {
		$color = "\x1b[91m";
	    }
            $freq = sprintf "%s%d Mhz\x1b[0m", $color, $freq;
        } else {
            $freq = sprintf "%dMhz", <$fh> / 1000;
        }    
        close $fh;
	$freq_str .= $freq;
        if($i < $#fnames) {
            $freq_str .= " & ";
        } 
    }

    if($#fnames > 0 && $f eq "") {
	$freq_str = "(" . $freq_str . ")";
    }

    if($freq_str eq "") {
	$freq_str = "Unknown";
    }
    return $freq_str;
}

sub get_arch_info {
    my $a=shift;
    my @cpus = </sys/devices/system/cpu/cpu?>;
    my @cpu_groups;
    my %group_core_count;
    for my $cpu (@cpus) {
        open my $fh, "<", "${cpu}/uevent";
        while(<$fh>) {
            chomp;
            my($key,$value) = split /=/;
            if($key eq "OF_COMPATIBLE_0") {
		my($cortex, $a) = split /-/, (split /,/, $value)[1];
                my $core_type_name = ucfirst($cortex) . "-" . ucfirst($a);
                if(not exists $group_core_count{$core_type_name}) {
                     push @cpu_groups, $core_type_name;
                     $group_core_count{$core_type_name} = 0;
                }
                $group_core_count{$core_type_name} += 1;
            } 
        }
        close $fh;
    }

    my $arch_info;
    if($a) {
        $arch_info = "AArch64 : " if ($a);
    } else {
        $arch_info = "AArch64 Processor : ";
    }
    for (my $i = 0; $i < $#cpu_groups + 1; $i++) {
        my $group = $cpu_groups[$i];
	#if($i == 0) {
	#    $arch_info .= "";
	#}
        $arch_info .= $group;
	if($a) {
            $arch_info .= " x ";
        } else {
            $arch_info .= " * ";
        }
        $arch_info .= $group_core_count{$group};
        if($i < $#cpu_groups) {
            $arch_info .= " & ";
        } #else {
	#    $arch_info .= "";
	#}
    }

    return "$arch_info";
}

sub get_uptime {
    my $fh;
    my $result;
    open $fh, "<", "/proc/uptime" or die;
    my $str_uptime = <$fh>;
    if($str_uptime =~ /(\d*)/) {
        my $day  = int($1/86400);
        my $hour = int(($1%86400)/3600);
	my $min  = int(($1%3600)/60);
	my $sec  =  $1%60;

	my $sday  = $day . "天 " if($day>0);
	my $shour = $hour . "小时 " if($hour>0 || $day>0) ;
	my $smin  = $min . "分钟 ";
	my $ssec  = $sec . "秒";
	$result  .= $sday . $shour . $smin . $ssec;
    } 
    close $fh;
    return $result;
}

sub get_avgload {
    my $fh;
    my $result;
    open $fh, "<", "/proc/loadavg" or die;
    my $str = <$fh>;
    $result = join(' ', (split(/\s+/, $str))[0,1,2]);
    close $fh;
    return $result;
}

sub get_eth_name {
    my $eth = shift;
    return (split /\//, $eth)[-1];
}

sub get_eth_operstate {
    my $eth = shift;
    my $fh;
    open $fh, "<", "$eth/operstate" or warn $!;
    my $operstate = <$fh>;
    close $fh;
    $operstate =~ s/\n//;
    return $operstate;
}

sub get_eth_driver {
    my $eth = shift;
    my $fh;
    open $fh, "<", "$eth/device/uevent" or warn $!;
    my $driver;
    while(<$fh>) {
        chomp;
	if(/^DRIVER=/) {
	    $driver = (split /=/, $_)[-1];
            last;	    
	}
    }
    close $fh;
    return $driver;
}

sub get_eth_duplex {
    my $eth = shift;
    my $fh;
    open $fh, "<", "$eth/duplex" or warn $!;
    my $duplex = <$fh>;
    close $fh;
    $duplex =~ s/\n//;
    return $duplex;
}

sub get_eth_speed {
    my $eth = shift;
    my $fh;
    open $fh, "<", "$eth/speed" or warn $!;
    my $speed = <$fh>;
    close $fh;
    $speed =~ s/\n//;
    if($speed == -1) {
        my $driver = &get_eth_driver("$eth");
	if($driver eq "virtio_net") {
	    $speed = "Maximum";
	} else {
            $speed = "";
	}
    } elsif($speed <= 100) {
        $speed .= "Mb/s";
    } else {
        $speed = sprintf "%.1fGb/s", $speed / 1000;
    }
    $speed =~ s/\.0//;
    return $speed;
}

sub get_eth_temperature {
    my($driver, $eth_name) = @_;
    my $ret = "";
    return $ret if (! -d "/proc/net/r8125/$eth_name");
    if($driver eq 'r8125') {
	my $fh;
	open $fh, "<", "/proc/net/r8125/$eth_name/temp" or die $!;
	while(<$fh>) {
            chomp;
	    if(/^Cel:/) {
               $ret = (split /:/, $_)[-1]."℃";
	       last;
	    }
	}
	close $fh;
    }	    
    return $ret;
}

sub get_ethlist() {
    my $board = &get_boardinfo;
    if($board eq "FastRhino R66S") {
	return reverse sort (</sys/class/net/e[tn]*>);
    } elsif($board =~ m/NLnet Watermelon Pi/i) {
	return reverse sort (</sys/class/net/e[tn]*>);
    } else {
     	return sort (</sys/class/net/e[tn]*>);   
    }
}

sub get_ethcount() {
     my @eth_ary = &get_ethlist;
     return $#eth_ary + 1;
}

sub get_ethinfo {
     my @eth_ary = &get_ethlist;
     my $ret = "[\n";
     for (my $i = 0; $i <= $#eth_ary; $i++) {
	     $ret .= "  {\n";
	     my $eth_name = &get_eth_name($eth_ary[$i]);
	     my $driver = &get_eth_driver($eth_ary[$i]);
	     my $operstate = &get_eth_operstate($eth_ary[$i]) eq "up" ? 1 : 0;
	     my $speed = &get_eth_speed($eth_ary[$i]);
	     my $duplex = &get_eth_duplex($eth_ary[$i]);
	     my $temperature = &get_eth_temperature($driver, $eth_name);
	     if($temperature eq "") {
                  $temperature = "☰";
	     }

	     if(($duplex eq "unknown")) {
                 if(($driver eq "virtio_net") && ($operstate == 1)) {
	            $duplex = "full";
		 }
             }

	     if($operstate == 1) {
	         $speed .= " $duplex duplex";
	     } else {
	         $speed = "☷";
	     }

	     $ret .= qq|    "name": "| . $eth_name . qq|",\n|;
	     $ret .= qq|    "driver": "| . $driver . qq|",\n|;
	     $ret .= qq|    "temperature": "| . $temperature . qq|",\n|;
	     $ret .= qq|    "operstate": | . $operstate . ",\n";
	     $ret .= qq|    "speed": "| . $speed . qq|"\n|;
	     if($i < $#eth_ary) {
	         $ret .= "  },\n";
	     } else {
	         $ret .= "  }\n";
	     }
     }
     $ret .= "]";
     return $ret;
}

sub get_archname() {
    return $Config{archname};
}

sub get_boardinfo() {
    my $ret="unknown";
    my $arch=&get_archname;
    if($arch =~ /^aarch64/) {
        if(-f "/proc/device-tree/model") {
             open my $fh, "<", "/proc/device-tree/model" or warn $!;
	     read $fh, $ret, 100;
	     close $fh;
	     $ret =~ s/\0//;
        } elsif(-d "/sys/firmware/qemu_fw_cfg") {
	     $ret = "QEMU KVM Virtual Machine";
	}
    } elsif($arch =~ /^x86/) {
	    open my $fh, "<", "/proc/cpuinfo" or warn $!;
	    while(<$fh>) {
                chomp;
		if(/^model name/) {
                     $ret = (split /:/, $_)[-1];
		     $ret =~ s/\A\s+//;
		}
	    }
    }
    return $ret;
}

sub get_dc_voltage() {
    my $precision = shift;
    my $board = &get_boardinfo;
    my $up_res = 0;
    my $down_res = 0;
    my $iio_channel;
    my $ret = 0;
    my $fh;
    if($board =~ m/Radxa ROCK 5B/) {
	$up_res = 100;
	$down_res = 8.2;
	$iio_channel = 6;
	$ret = "(Probably) 12";
    }

    if($up_res && $down_res) {
        my $voltage_raw = 0;
        my $voltage_scale = 0;
	open $fh, "<", "/sys/bus/iio/devices/iio:device0/in_voltage_scale" or die;
	read $fh, $voltage_scale, 40;
	close $fh;

	open $fh, "<", "/sys/bus/iio/devices/iio:device0/in_voltage${iio_channel}_raw" or die;
	read $fh, $voltage_raw, 40;
	close $fh;

        if($voltage_raw && $voltage_scale && (($voltage_raw + 1) * $voltage_scale < 1800)) {
	    $precision = 0 unless $precision;
	    my $fmt = "%0.${precision}f";
            $ret = sprintf $fmt, $voltage_raw * $voltage_scale * ($up_res + $down_res) / $down_res / 1000.0;
	}
    }
    return $ret;
}

#####   main program #####
my $argc = scalar( @ARGV );
if($argc == 0) {
    print &get_cpu_freq, " / ", &get_soc_temp, "\n";
} elsif($ARGV[0] eq "-a") {
    print &get_arch_info, "\n";
} elsif($ARGV[0] eq "-A") {
    print &get_arch_info("A"), "\n";
} elsif($ARGV[0] eq "-t") {
    print &get_soc_temp, "\n";
} elsif($ARGV[0] eq "-T") {
    print &get_soc_temp("T"), "\n";
} elsif($ARGV[0] eq "-f") {
    print &get_cpu_freq, "\n";
} elsif($ARGV[0] =~ /-F/) {
    print &get_cpu_freq($ARGV[0]), "\n";
} elsif($ARGV[0] eq "-u") {
    print &get_uptime, "\n";
} elsif($ARGV[0] eq "-l") {
    print &get_avgload, "\n";
} elsif($ARGV[0] eq "-e") {
    print &get_ethinfo, "\n";
} elsif($ARGV[0] eq "-ec") {
    print &get_ethcount, "\n";
} elsif($ARGV[0] eq "-b") {
    print &get_boardinfo, "\n";
} elsif($ARGV[0] =~ m/-V(\d{0,1})/) {
    print &get_dc_voltage($1), "\n";
}
